package think.in.java.concurrency.PractiseOneMoreTime;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Add a BusBoy class to Restaurant.java. After the meal is delivered, the WaitPerson should notify the BusBoy to clean up.
 * note, we should not handlethe interrupt exception in the called method which have sleep, as during sleep, if there is
 * a interruption, the signal will be reset when sleep met the exception
 */
public class Exercise26RestaurantWaitNotify {
    private Meal meal;
    private Waiter waiter;
    private Chef chef;
    private BusyBoby busyBoby;



    public Chef getChef() {
        return chef;
    }

    public void setChef(Chef chef) {
        this.chef = chef;
    }

    public Meal getMeal() {
        return meal;
    }

    public void setMeal(Meal meal) {
        this.meal = meal;
    }

    public Waiter getWaiter() {
        return waiter;
    }

    public void setWaiter(Waiter waiter) {
        this.waiter = waiter;
    }

    public static void main(String[] args) throws InterruptedException {
        Exercise26RestaurantWaitNotify restaurantWaitNotify = new Exercise26RestaurantWaitNotify();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Waiter waiter1 = restaurantWaitNotify.new Waiter(restaurantWaitNotify);
        Chef chef1 = restaurantWaitNotify.new Chef(restaurantWaitNotify);
        BusyBoby busyBoby1 = restaurantWaitNotify.new BusyBoby(restaurantWaitNotify);
        restaurantWaitNotify.setChef(chef1);
        restaurantWaitNotify.setWaiter(waiter1);
        restaurantWaitNotify.setBusyBoby(busyBoby1);
        executorService.execute(busyBoby1);
        executorService.execute(waiter1);
        executorService.execute(chef1);

        Thread.sleep(1000);
        executorService.shutdownNow();
    }

    public BusyBoby getBusyBoby() {
        return busyBoby;
    }

    public void setBusyBoby(BusyBoby busyBoby) {
        this.busyBoby = busyBoby;
    }



    class Meal {

    }

    class BusyBoby implements Runnable {
        Exercise26RestaurantWaitNotify restaurantWaitNotify;

        BusyBoby(Exercise26RestaurantWaitNotify restaurantWaitNotify) {
            this.restaurantWaitNotify = restaurantWaitNotify;
        }

        @Override
        public void run() {
            try {
                while (!Thread.interrupted()) {

                    synchronized (this) {
                        this.wait();
                    }

                    clean();

                }
            } catch (InterruptedException e) {
                System.out.println("Restaurant closed.");
            }
        }

        private void clean() throws InterruptedException {
            Thread.yield();

            System.out.println(Thread.currentThread().getName() + " cleaning.");
            Thread.sleep(50);
            System.out.println(Thread.currentThread().getName() + " cleaned.");

        }
    }

    class Chef implements Runnable {
        Exercise26RestaurantWaitNotify restaurantWaitNotify;

        Chef(Exercise26RestaurantWaitNotify restaurantWaitNotify) {
            this.restaurantWaitNotify = restaurantWaitNotify;
        }

        @Override
        public void run() {
            try {
                while (!Thread.interrupted()) {
                    if (restaurantWaitNotify.getMeal() != null) {
                        synchronized (this) {
                            this.wait();
                        }
                    }
                    cook();
                    synchronized (restaurantWaitNotify.getWaiter()) {
                        restaurantWaitNotify.getWaiter().notify();
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("Restaurant closed.");
            }
        }

        private void cook() throws InterruptedException {
            Thread.yield();

            System.out.println(Thread.currentThread().getName() + " Cooking.");
            Thread.sleep(100);
            restaurantWaitNotify.setMeal(new Meal());
            System.out.println(Thread.currentThread().getName() + " cooked.");

        }
    }

    class Waiter implements Runnable {
        Exercise26RestaurantWaitNotify restaurantWaitNotify;

        Waiter(Exercise26RestaurantWaitNotify restaurantWaitNotify) {
            this.restaurantWaitNotify = restaurantWaitNotify;
        }

        @Override
        public void run() {
            try {
                while (!Thread.interrupted()) {
                    if (restaurantWaitNotify.getMeal() == null) {
                        synchronized (this) {
                            this.wait();
                        }
                    }
                    deliver();
                    synchronized (restaurantWaitNotify.getChef()) {
                        restaurantWaitNotify.getChef().notify();
                    }
                    synchronized (restaurantWaitNotify.getBusyBoby()){
                        restaurantWaitNotify.getBusyBoby().notify();
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("Restaurant closed.");
            }
        }

        private void deliver() throws InterruptedException {
            Thread.yield();
//        try {
            System.out.println(Thread.currentThread().getName() + " delivering.");
            Thread.sleep(100);
            restaurantWaitNotify.setMeal(null);
            System.out.println(Thread.currentThread().getName() + " delivered.");
//        } catch (InterruptedException e) {
// todo note, we should not handlethe interrupt exception in the called method which have sleep, as during sleep, if there is
            //todo a interruption, the signal will be reset when sleep met the exception
//            System.out.println("Restaurant closed.");
//        }
        }
    }
}



